package com.pine.app.module.security.oauth.provider.token;


import com.pine.app.module.security.core.common.enums.ErrorType;
import com.pine.app.module.security.oauth.exception.AuthenticationException;
import com.pine.app.module.security.oauth.provider.DefaultOAuth2RefreshToken;
import com.pine.app.module.security.oauth.provider.OAuth2Authentication;
import com.pine.app.module.security.oauth.provider.OAuth2ClientRequest;
import com.pine.app.module.security.oauth.provider.client.ClientDetails;
import com.pine.app.module.security.oauth.provider.client.ClientDetailsService;
import com.pine.app.module.security.oauth.provider.token.store.TokenStore;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.UUID;

/**
 * @author xiaoyuan
 * @create 2019/10/8 15:20
 **/

public class AccessTokenServiceImpl implements AccessTokenService {

    private final int accessTokenValiditySeconds = 86400;
    private final  int refreshTokenValiditySeconds = 2592000;

    private TokenStore tokenStore;
    private ClientDetailsService clientDetailsService;


    @Autowired
    public void setTokenStore(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }
    @Autowired
    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    @Override
    public OAuth2AccessToken createToken(OAuth2Authentication authentication) {
        OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication);
        if (existingAccessToken != null) {
            this.tokenStore.removeAccessToken(existingAccessToken);
        }
        OAuth2RefreshToken oAuth2RefreshToken = createRefreshToken(authentication);
        OAuth2AccessToken auth2AccessToken = createAccessToken(authentication, oAuth2RefreshToken);
        this.tokenStore.storeAccessToken(auth2AccessToken, authentication);
        return auth2AccessToken;
    }

    @Override
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue) {
        OAuth2AccessToken auth2AccessToken = this.tokenStore.readRefreshToken(refreshTokenValue);
        //获取token
        OAuth2Authentication oAuth2Authentication = this.tokenStore.readRefreshToAuthentication(refreshTokenValue);
        if (auth2AccessToken == null || oAuth2Authentication == null) {
            ErrorType.ILLEGAL_REFRESH_TOKEN.throwThis(AuthenticationException::new);

        }
        OAuth2AccessToken auth2Access = this.tokenStore.readAccessToken(auth2AccessToken.getAccessToken());
        if (auth2Access == null) {
            OAuth2RefreshToken oAuth2RefreshToken = auth2AccessToken.getOauth2RefreshToken();
            auth2AccessToken = createAccessToken(oAuth2Authentication, oAuth2RefreshToken);
            this.tokenStore.storeAccessToken(auth2AccessToken, oAuth2Authentication);
        }
        return auth2AccessToken;
    }

    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        return this.tokenStore.getAccessToken(authentication);
    }

    @Override
    public OAuth2AccessToken getAccessToken(String accessToken) {
        return this.tokenStore.readAccessToken(accessToken);
    }

    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) {
        return this.tokenStore.readAuthentication(accessToken);
    }

    @Override
    public boolean removeToken(String accessToken) {
        OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
        if (oAuth2AccessToken == null) {
            return true;
        }
        this.tokenStore.removeAccessToken(accessToken);
        if (StringUtils.isNotBlank(oAuth2AccessToken.getRefreshToken())) {
            this.tokenStore.removeRefreshToken(oAuth2AccessToken.getRefreshToken());
        }
        return true;
    }


    private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken oAuth2RefreshToken) {
        SimpleOAuth2AccessEntity oAuth2AccessToken = new SimpleOAuth2AccessEntity(UUID.randomUUID().toString());
        oAuth2AccessToken.setScope(authentication.getTokenRequest().getScope());
        oAuth2AccessToken.setOauth2RefreshToken(oAuth2RefreshToken);
        int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getTokenRequest());
        if (validitySeconds > 0) {
            oAuth2AccessToken.setExpireTime(new Date(System.currentTimeMillis() + (long) validitySeconds * 1000L));
        }
        return oAuth2AccessToken;
    }

    private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
        if (authentication.isEnableRefresh()) {
            DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken();
            refreshToken.setToken(UUID.randomUUID().toString());
            int validitySeconds = this.getRefreshTokenValiditySeconds(authentication.getTokenRequest());
            if (validitySeconds > 0) {
                refreshToken.setExpireTime(new Date(System.currentTimeMillis() + (long) validitySeconds * 1000L));
            }
            return refreshToken;
        }
        return null;

    }

    protected int getAccessTokenValiditySeconds(OAuth2ClientRequest tokenRequest) {
        if (tokenRequest.getAccessTokenValiditySeconds() != null) {
            return tokenRequest.getAccessTokenValiditySeconds();
        }
        if (this.clientDetailsService != null) {
            ClientDetails client = this.clientDetailsService.loadClientDetail(tokenRequest.getClientId());
            Integer validity = client.getAccessTokenValiditySeconds();
            if (validity != null) {
                return validity.intValue();
            }
        }

        return this.accessTokenValiditySeconds;
    }

    protected int getRefreshTokenValiditySeconds(OAuth2ClientRequest tokenRequest) {
        if (tokenRequest.getRefreshTokenValiditySeconds() != null) {
            return tokenRequest.getRefreshTokenValiditySeconds();
        }
        if (this.clientDetailsService != null) {
            ClientDetails client = this.clientDetailsService.loadClientDetail(tokenRequest.getClientId());
            Integer validity = client.getRefreshTokenValiditySeconds();
            if (validity != null) {
                return validity.intValue();
            }
        }

        return this.refreshTokenValiditySeconds;
    }


}
